#include "map.h"

#include "player.h"

/**
 * 17.3.12 - Steven Hartin / Stephen Gore - Added some basic tile face occlusion
 *  Will not render sides of faces of the tiles which cannot be seen.
 *
 * 15.3.12 - Stephen Gore - Original Creation.
 */

#define rotation_speed 0.01f

CMap::CMap(bool bDemo)
    :
    m_szMapName("unnamed"),
    
    // Map Size
    m_nHeight(0),
    m_nWidth(0),
    m_nDepth(0),

    // The droppable tiles start moving up first
    m_nTileMovement(MOVE_UP),

    // Amount of things on the map 
    m_nAmountOfPickups(0),
    //m_nAmountOfDestructibleTiles(0),
    m_nAmountOfTiles(0),
    m_nAmountOfEntities(0),
    m_nNoOfPlayers(0),

    m_fDroppableXPosition(Constants::TILE_SIZE),
    m_fDroppableZPosition(Constants::TILE_SIZE),

    m_fDroppableXMax(Constants::TILE_SIZE),
    m_fDroppableZMax(Constants::TILE_SIZE),

    m_fDroppableXMin(Constants::TILE_SIZE),
    m_fDroppableZMin(Constants::TILE_SIZE),

    m_nTimeLeft(180),
    m_nTimeCountingDown(5),

    //m_StartRepeatTimer(5000.0f, &mapfuncs::startMap, 1),
    m_DropTileRepeat(0.0f, &mapfuncs::dropTiles),
    m_CounterRepeat(1000.0f, &mapfuncs::decrementTime, 185),

#ifdef _DEBUG
    m_moveCameraRepeat(10.0f, &mapfuncs::moveCamera, 10),
#else
    m_moveCameraRepeat(10.0f, &mapfuncs::moveCamera, 250),
    //CRepeat<void> myRepeat(10.0f, &moveCamera);
#endif

    m_pGameSound(nullptr),
    m_pBackground(nullptr),
    m_pMapTimerUI(nullptr),

    m_pPauseBackground(nullptr),
    m_pEndBackground(nullptr),
    
    m_bPaused(true),

    m_pWinner(nullptr),

    m_fCameraRotationAngle(0.0f),

    m_fIntroCameraMovementInterpolation(0.0f),

    m_nEndState(IN_PROGRESS),

    m_bDemo(bDemo)
    
{
    /**
     * Constructor, initialise the map to default attributes
     */
    if (!m_bDemo)
    {
        m_DropTileRepeat.registerParam(&m_DropTileRepeat, 
            utils::TYPE_POINTER, sizeof(CRepeat<void>));

        m_DropTileRepeat.registerParam(this,
            utils::TYPE_POINTER, sizeof(CMap));

        m_DropTileRepeat.registerParam(&m_fDroppableXPosition,
            utils::TYPE_POINTER, sizeof(float));

        m_DropTileRepeat.registerParam(&m_fDroppableZPosition,
            utils::TYPE_POINTER, sizeof(float));

        m_CounterRepeat.registerParam(this,
            utils::TYPE_POINTER, sizeof(CMap));

        m_pBackground = new CUISprite("textures/ui/game/clock.png");
        m_pBackground->setMarginLeft(526);
        m_pBackground->setMarginTop(10);

        m_pMapTimerUI = new CUIText("3:00", "bombermansmall");
        m_pBackground->addChild("timer", m_pMapTimerUI);
        m_pMapTimerUI->setMarginLeft(87);
        m_pMapTimerUI->setMarginTop(17);
        m_pMapTimerUI->setColour(255, 255, 255);

        CUI::getInstance()->addChild("clock", m_pBackground);

        m_pPauseBackground = new CUISprite(
            "textures/ui/game/pause_screen_neutral.png");
        CUI::getInstance()->addChild("pause", m_pPauseBackground);
        m_pPauseBackground->setMarginLeft(240);
        m_pPauseBackground->setMarginTop(60);
        m_pPauseBackground->hide();

        m_pEndBackground = new CUISprite(
            "textures/ui/game/pause_screen_neutral.png");
        CUI::getInstance()->addChild("end", m_pEndBackground);
        m_pEndBackground->setMarginLeft(240);
        m_pEndBackground->setMarginTop(60);
        m_pEndBackground->hide();

        m_CounterRepeat.start();
    }

    resetCameraToDefault();

    m_moveCameraRepeat.registerParam(&m_fIntroCameraMovementInterpolation,
        utils::TYPE_POINTER, sizeof(float));

    m_moveCameraRepeat.start();
}

CMap::~CMap()
{
    /**
     * Ensure that the current map is unloaded when this map is destroyed.
     */
    unload();
}

void CMap::decrementTime()
{
    /**
     * 
     */
    int nTimeLeft(0);
    if (m_nTimeCountingDown > 0)
    {
        --m_nTimeCountingDown;
        nTimeLeft = m_nTimeCountingDown;
        if (m_nTimeCountingDown == 0)
        {
            CSound *pSound(CSoundManager::getInstance()->addStream("play.mp3"));
            pSound->setVolume(0.5f);
            pSound->createNewChannel(&m_pGameSound);
            m_pGameSound->setLoopCount(-1);
            m_pGameSound->setMode(FMOD_LOOP_NORMAL);
            m_pGameSound->setPaused(false);
            nTimeLeft = m_nTimeLeft;

            CSound *pSound2(CSoundManager::getInstance()->addSound(
                "go.wav"));
            FMOD::Channel *pNewChannel(nullptr);
            pSound2->createNewChannel(&pNewChannel);
            pNewChannel->setPaused(false);

            m_bPaused = false;
        }
        if (m_nTimeCountingDown == 4)
        {
            CSound *pSound(CSoundManager::getInstance()->addSound(
                "play_intro.mp3"));
            pSound->setVolume(0.5f);
            FMOD::Channel *pNewChannel(nullptr);
            pSound->createNewChannel(&pNewChannel);
            pNewChannel->setPaused(false);
        }

        if (m_nTimeCountingDown == 2)
        {
            CSound *pSound(CSoundManager::getInstance()->addSound(
                "ready.wav"));
            FMOD::Channel *pNewChannel(nullptr);
            pSound->createNewChannel(&pNewChannel);
            pNewChannel->setPaused(false);
        }
    }
    else
    {
        --m_nTimeLeft;
        if (m_nTimeLeft == 60)
        {
            CSound *pSound(CSoundManager::getInstance()->addSound(
                "hurry_up.wav"));
            pSound->setVolume(0.5f);
            FMOD::Channel *pNewChannel(nullptr);
            pSound->createNewChannel(&pNewChannel);
            pNewChannel->setPaused(false);

            m_pMapTimerUI->setColour(255, 0, 0);

            m_DropTileRepeat.start();
        }
        if (m_nTimeLeft <= 0)
        {
            drawMatch(true);
        }
        nTimeLeft = m_nTimeLeft;
    }
    stringstream sstream;
    sstream << (nTimeLeft / 60) << ":"
            << std::setfill('0') 
            << std::setw(2)
            << (nTimeLeft % 60);
    m_pMapTimerUI->setText(sstream.str());
}

int CMap::getHeight() const
{
    /**
     * @return int The height of the map.
     */ 
    return m_nHeight;
}

int CMap::getWidth() const
{
    /**
     * @return int The width of the map.
     */ 
    return m_nWidth;
}

int CMap::getDepth() const
{
    /**
     * @return int The depth of the map.
     */ 
    return m_nDepth;
}

CTile *CMap::getTile(int y, int x, int z) const
{
    /**
     * @return CTILE* Returns a pointer to the tile at the position passed in  
     */ 
    return m_MapData.at(y).at(x).at(z);
}

FLOOR CMap::getFloor(int y) const
{
    /**
     * @return FLOOR Returns a vector of pointers to CTILES - a whole floor
     */ 
    return m_MapData.at(y);
}

WIDTHCOLUMN CMap::getWidthColumn(int z) const
{
    /**
     *
     * Gets a whole width column on the z position passed in.
     *
     * @param int z The width column to return.
     *
     * @return WIDTHCOLUMN Returns a vector of pointers to CTILES - a whole 
     * width column.
     */ 

    // Create a new width column to return.
    WIDTHCOLUMN widthColumn;
    for (int i(0); i < m_nHeight; ++i)
    {
        // For every floor in the width column create a new row.
        ROW row;
        for (int j(0); j < m_nWidth; ++j)
        {
            // For every tile in the row add it to the row.
            row.push_back(m_MapData.at(i).at(j).at(z));
        }
        // Add the new row to the width column.
        widthColumn.push_back(row);
    }
    // Return the width column.
    return widthColumn;
}

DEPTHCOLUMN CMap::getDepthColumn(int x) const
{
    /**
     *
     * Gets a whole depth column on the x position passed in.
     *
     * @param int x The depth column to return.
     *
     * @return DEPTHCOLUMN Returns a vector of pointers to CTILES - a whole 
     * depth column.
     */ 
    DEPTHCOLUMN depthColumn;
    for (int i(0); i < m_nHeight; ++i)
    {
        // For every floor in the depth column create a new row.
        ROW row;
        for (int j(0); j < m_nDepth; ++j)
        {
            // For each tile in the row add it to the row 
            row.push_back(m_MapData.at(i).at(x).at(j));
        }
         // Add the new row to the width column.
        depthColumn.push_back(row);
    }
     // Return the depth column.
    return depthColumn;
}

MAP CMap::getCube(int y, int x, int z, int radius) const
{
    /**
     *
     * Returns a cube of tiles around a point given. This is used to test what 
     * tiles a bomb will effect.
     *
     * @param int y The y coordinate.
     * @param int x The x coordinate.
     * @param int z The z coordinate.
     * @param int radius The amount of tiles to get in each direction.
     *
     * @return MAP The vector of vectors containing pointers to the tiles around
     * the point given in 
     */ 

    // Used to check if the radius goes out of the map.
    int yMin, yMax, xMin, xMax, zMin, zMax;

    // Set the y max to y + radius if it is in the map if not set it to the map 
    // edge.
    if ((y + radius) > m_nHeight)
    {
        yMax = m_nHeight; 
    } 
    else 
    {
        yMax = y + radius; 
    }

    // Set the y min to y - radius if it is in the map if not set it to the map 
    // edge.
    if ((y - radius) < 0)
    {
        yMin = 0; 
    } 
    else 
    {
        yMin = y - radius; 
    }

    // Set the x max to x + radius if it is in the map if not set it to the map 
    // edge.
    if ((x + radius) > m_nWidth)
    {
        xMax = m_nWidth; 
    } 
    else 
    {
        xMax = x + radius; 
    }

    // Set the x min to x - radius if it is in the map if not set it to the map 
    // edge.
    if ((x - radius) < 0)
    {
        xMin = 0; 
    } 
    else 
    {
        xMin = x - radius; 
    }
    // Set the z max to z + radius if it is in the map if not set it to the map 
    // edge.
    if ((z + radius) > m_nDepth)
    {
        zMax = m_nDepth; 
    } 
    else 
    {
        zMax = z + radius; 
    }

    // Set the z min to z - radius if it is in the map if not set it to the map 
    // edge.
    if ((z - radius) < 0)
    {
        zMin = 0; 
    } 
    else 
    {
        zMin = z - radius; 
    }

    // Create a new map - vector of rows. 
    MAP map;
    
    for (int i(yMin); i < yMax; ++i)
    {
        // For each floor create a floor for the cube.
        FLOOR floor;
        for (int j(xMin); j < xMax; ++j)
        {
            // For each row create a row for the cube.
            ROW row;
            for (int k(zMin); k < zMax; ++k)
            {
                // For each tile in the cube add it to the row.
                row.push_back(getTile(i,j,k));
            }
            // Add the row to the floor.
            floor.push_back(row);
        }
        // Add the floor to the cube.
        map.push_back(floor);
    }

    // Return the cube.
    return map;
}

void CMap::setPickupIdCount(short int nPickupID, long int nPickupCount)
{
    /**
     * Sets the pickup ID and count of pickups in the map to a new value. This
     * is to allow us to regenerate the map quickly without needing to
     * read the map file again.
     *
     * @param short int nPickupID The ID of the pickup
     * @param long int nPickupCount The amount of pickups which exist on the map
     */
    m_arrPickupIds.insert(pair<short int, long>(nPickupID, nPickupCount));
}

long int CMap::getPickupIdCount(short int nPickupID) const
{
    /**
     * @param short int nPickupID The ID of the pickup
     * @return long int THe amount of pickups by the type on the map
     */
    map<short, long>::const_iterator pPos(m_arrPickupIds.find(nPickupID));
    if (pPos != m_arrPickupIds.end())
    {
        return pPos->second;
    }
    return 0;
}

vector <short int> CMap::getPickupIds() const
{
    /**
     * @return vector<short int> A vector containing an array of pickup ids
     *  which exist on the map.
     */
    vector<short int> arrIds;
    for (map<short, long>::const_iterator pPos(m_arrPickupIds.begin());
         pPos != m_arrPickupIds.end(); ++pPos)
    {
        arrIds.push_back(pPos->first);
    }
    return arrIds;
}

void CMap::addDestructibleTileCount(short int nTileID, short int nAmount)
{
    /**
     * @param nTileID The tile id of the destructible tiles
     * @param short int nAmount The amount of the tiles to make
     */
    m_arrDestructibleTiles.insert(pair<short int, short int>(nTileID, nAmount));
}

vector<short int> CMap::getDestructibleTileIds() const
{
    /**
     * @return vector<short int> A vector containing an array of destructible
     *  tile ids which exist on the map.
     */
    vector<short int> arrIds;
    for (map<short, short>::const_iterator pPos(m_arrDestructibleTiles.begin());
         pPos != m_arrDestructibleTiles.end(); ++pPos)
    {
        arrIds.push_back(pPos->first);
    }
    return arrIds;
}

short int CMap::getDestructibleIdCount(short int nTileID)
{
    /**
     * @param short int nTileID The ID of the tile
     * @return short int THe amount of pickups by the type on the map
     */
    map<short, short>::const_iterator pPos(
        m_arrDestructibleTiles.find(nTileID));
    if (pPos != m_arrDestructibleTiles.end())
    {
        return pPos->second;
    }
    return 0;
}

CPickup *CMap::getPickupFromIndex(size_t nIndex) const
{
    /**
     * @param size_t nIndex The index of which to obtain the index at
     * @return CPickup* A pointer to the pickup (nullptr if none existant)
     */
    if (nIndex < m_arrPickups.size())
    {
        try
        {
            return m_arrPickups.at(nIndex);
        }
        catch (std::out_of_range)
        {
            return nullptr;
        }
    }
    return nullptr;
}

MAP CMap::getCube(int y, int x, int z, int height, int width, int depth) const
{
    /**
     *
     * Returns a cube of tiles from the given point given. This is used to test
     * what tiles a bomb will effect.
     *
     * @param int y The centre y coordinate.
     * @param int x The centre x coordinate.
     * @param int z The centre z coordinate.
     * @param int height The height radius of the cube.
     * @param int width The width radius of the cube.
     * @param int depth The depth radius of the cube.
     *
     * @return MAP The vector of vectors containing pointers to the tiles around
     * the point given in.
     */ 
    int yMin, yMax, xMin, xMax, zMin, zMax;

    // Work out the radius to go either side of the centre point
    int heightRadius(height/2);
    int widthRadius(width/2);
    int depthRadius(depth/2);

    // Set the y max to y + radius if it is in the map if not set it to the map 
    // edge.
    if ((y + heightRadius) > m_nHeight)
    {
        yMax = m_nHeight; 
    } 
    else 
    {
        yMax = y + heightRadius; 
    }

    // Set the y min to y - radius if it is in the map if not set it to the map 
    // edge.
    if((y - heightRadius) < 0)
    {
        yMin = 0; 
    } 
    else 
    {
        yMin = y - heightRadius; 
    }

    // Set the x max to x + radius if it is in the map if not set it to the map 
    // edge.
    if((x + widthRadius) > m_nWidth)
    {
        xMax = m_nWidth; 
    } 
    else 
    {
        xMax = x + widthRadius; 
    }

    // Set the x min to x - radius if it is in the map if not set it to the map 
    // edge.
    if((x - widthRadius) < 0)
    {
        xMin = 0; 
    } 
    else 
    {
        xMin = x - widthRadius; 
    }

    // Set the z max to z + radius if it is in the map if not set it to the map 
    // edge.
    if((z + depthRadius) > m_nDepth)
    {
        zMax = m_nDepth; 
    } 
    else 
    {
        zMax = z + depthRadius; 
    }

    // Set the z min to z - radius if it is in the map if not set it to the map 
    // edge.
    if ((z - depthRadius) < 0)
    {
        zMin = 0; 
    } 
    else 
    {
        zMin = z - depthRadius; 
    }

    MAP map;

    for (int i(yMin); i < yMax; ++i)
    {
        // For each floor create a floor for the cube.
        FLOOR floor;
        for (int j(xMin); j < xMax; ++j)
        {
            // For each row create a row for the cube.
            ROW row;
            for (int k(zMin); k < zMax; ++k)
            {
                // For each tile in the cube add it to the row.
                row.push_back(getTile(i,j,k));
            }
            // Add the row to the flor.
            floor.push_back(row);
        }
        // Add the floor to the cube.
        map.push_back(floor);
    }

    // Return the cube.
    return map;
}

void CMap::getTilePositionFromCoord(int &nHeight, int &nWidth, int &nDepth,
                                    float fX, float fY, float fZ, int nOffset)
                                    const
{
    /**
     * Obtains the current map tile position from a given coord position.
     *
     * @param int &nHeight A variable to store the height tile value
     * @param int &nWidth A variable to store the width tile value
     * @param int &nDepth A variable to store the depth tile value
     * @param float fX The X coordinate of the object
     * @param float fY The Y coordinate of the object
     * @param float fZ The Z coordinate of the object
     */
    nHeight = static_cast<unsigned int>(fY + (Constants::TILE_SIZE / 2.0f)) / 
          static_cast<unsigned int>(Constants::TILE_SIZE);
    nWidth = static_cast<unsigned int>(fX + (Constants::TILE_SIZE / 2.0f))  / 
          static_cast<unsigned int>(Constants::TILE_SIZE);
    nDepth = static_cast<unsigned int>(fZ + (Constants::TILE_SIZE / 2.0f)) / 
          static_cast<unsigned int>(Constants::TILE_SIZE);
}

BYTE CMap::getMaxPlayerCount() const
{
    return m_nNoOfPlayers;
}

int CMap::getPickupCount() const
{
    return m_nAmountOfPickups;
}

int CMap::getTileCount() const
{
    return m_nAmountOfTiles;
}

int CMap::getDestructibleTileCount() const
{
    int nAmount(0);
    for (map<short, short>::const_iterator pPos(m_arrDestructibleTiles.begin());
         pPos != m_arrDestructibleTiles.end(); ++pPos)
    {
        nAmount += pPos->second;
    }
    return nAmount;
}

int CMap::getEntityCount() const
{
    return m_nAmountOfEntities;
}

void CMap::setMapName(string szMapName)
{
    m_szMapName = szMapName;
}

void CMap::setSize(short int nHeight, short int nWidth, short int nDepth)
{
    m_nHeight = nHeight;
    m_nWidth = nWidth;
    m_nDepth = nDepth;

    m_fDroppableXMax = Constants::TILE_SIZE * (m_nWidth - 2);
    m_fDroppableZMax = Constants::TILE_SIZE * (m_nDepth - 2);
}

void CMap::setPickupCount(short int nPickupCount)
{
    m_nAmountOfPickups = nPickupCount;
}

void CMap::setTileCount(long int nTileCount)
{
    m_nAmountOfTiles = nTileCount;
}

void CMap::setEntityCount(short int nEntityCount)
{
    m_nAmountOfEntities = nEntityCount;
}

void CMap::setMaxPlayerCount(BYTE nPlayerCount)
{
    m_nNoOfPlayers = nPlayerCount;
}

void CMap::addPickup(CPickup *pPickup)
{
    m_arrPickups.push_back(pPickup);
}

void CMap::addEntity(CEntity *pEntity)
{
    m_arrEntitys.push_back(pEntity);
}

void CMap::addFloor(FLOOR &floor)
{
    m_MapData.push_back(floor);
}

void CMap::addFloorIds(vector<vector<short int>> arrTileIds)
{
    m_arrTileIDs.push_back(arrTileIds);
}

void CMap::pause()
{
    CSound *pSound(CSoundManager::getInstance()->addSound(
        "pause.wav"));
    FMOD::Channel *pNewChannel(nullptr);
    pSound->createNewChannel(&pNewChannel);
    pNewChannel->setPaused(false);

    m_DropTileRepeat.pause();
    m_CounterRepeat.pause();

    m_pPauseBackground->show();

    m_bPaused = true;

    CMenuContainer::getInstance()->getMenu("pause_menu")->setActive(true);

}

void CMap::resume()
{
    if (m_CounterRepeat.getRepeatCount() <= 30)
    {
        m_DropTileRepeat.start();
    }
    m_CounterRepeat.start();

    m_pPauseBackground->hide();

    m_bPaused = false;

    CMenuContainer::getInstance()->getMenu("pause_menu")->setActive(false);
}

void CMap::reset()
{
    m_DropTileRepeat.stop();
    m_CounterRepeat.stop();
    
    for (MAP::iterator pFloor(m_MapData.begin()); pFloor != m_MapData.end(); 
         ++pFloor)
    {
        for (FLOOR::iterator pRow(pFloor->begin()); pRow != pFloor->end(); 
            ++pRow)
        {
            for (ROW::iterator pTile(pRow->begin()); pTile != pRow->end(); 
                 ++pTile)
            {
                delete *pTile;
            }
        }
    }
    m_MapData.clear();

    m_fDroppableXPosition = Constants::TILE_SIZE;
    m_fDroppableZPosition = Constants::TILE_SIZE;

    m_fDroppableXMax = Constants::TILE_SIZE * (getWidth() - 2);
    m_fDroppableZMax = Constants::TILE_SIZE * (getDepth() - 2);

    m_fDroppableXMin = Constants::TILE_SIZE;
    m_fDroppableZMin = Constants::TILE_SIZE;

    m_nTileMovement = MOVE_UP;

    // Clear up the pickups
    m_arrPickups.clear();

    m_DropTileRepeat.setRepeatDelay(0.0f);
    m_DropTileRepeat.setRepeatCount(REPEAT_INDEFINITELY);

    m_CounterRepeat.setRepeatCount(180);
    m_CounterRepeat.start();

#ifdef _DEBUG
    m_moveCameraRepeat.setRepeatCount(10);
#else
    m_moveCameraRepeat.setRepeatCount(250);
#endif
    m_moveCameraRepeat.start();

    m_nTimeLeft = 180;
    m_nTimeCountingDown = 5;

    m_fCameraRotationAngle = 0.0f;

    m_pMapTimerUI->setColour(255, 255, 255);

    m_pGameSound->stop();
    m_pEndSound->stop();

    m_bPaused = true;

    m_pWinner = nullptr;
    m_nEndState = IN_PROGRESS;

    resetCameraToDefault();
    
    CLight *pGlobal(CLightContainer::getInstance()->getLight("sunlight"));
    pGlobal->setAmbient(0.9f, 0.9f, 0.8f, 1.0f);
    pGlobal->setDiffuse(0.9f, 0.9f, 0.8f, 1.0f);

    CLightContainer::getInstance()->setLight("sunlight", 0, 
        CDeviceManager::getInstance()->getDevice());

    CLightContainer::getInstance()->disableLight(1, 
        CDeviceManager::getInstance()->getDevice());

}

void CMap::resetCameraToDefault()
{
    CCamera *pCamera(CCamera::getInstance());
    D3DXVECTOR3 vecLookAt, vecPosition;
    vecLookAt.x = Constants::TILE_SIZE * getWidth() / 2;
    if (getWidth() % 2)
    {
        // Odd amount, we have to increase the look at x by half tile size
        vecLookAt.x -= Constants::TILE_SIZE / 2;
    }

    vecLookAt.z = Constants::TILE_SIZE * getDepth() / 2;
    if (getDepth() % 2)
    {
        // Odd amount, we have to increase the look at z by half tile size
        vecLookAt.z -= Constants::TILE_SIZE / 2;
    }

    vecLookAt.y = Constants::TILE_SIZE * (max(getDepth(), getWidth()) + 1);

    vecPosition = vecLookAt;

    vecPosition.z = Constants::CAMERA_POSITION.z;

    pCamera->setLookAt(vecLookAt);
    pCamera->setPosition(vecPosition);


#ifdef _DEBUG
    m_fIntroCameraMovementInterpolation = (vecLookAt.y - 
        (Constants::TILE_SIZE * getHeight())) / 10.0f;
#else
    m_fIntroCameraMovementInterpolation = (vecLookAt.y - 
        (Constants::TILE_SIZE * getHeight())) / 250.0f;
#endif


    pCamera->applyViewMatrix();
    pCamera->applyProjectionMatrix();
}

void CMap::endGame()
{
    /**
     * This doesn't actually end the game, but just puts it in an 'unplayable'
     * and pausable state. By this I mean all repeats are paused, and the paused
     * state is set to true.
     */
    if (! m_bPaused)
    {
        m_DropTileRepeat.pause();
        m_CounterRepeat.pause();

        m_bPaused = true;

        m_pGameSound->stop();
        CSound *pSound(CSoundManager::getInstance()->addStream("ending.mp3"));
        pSound->setVolume(1.0f);
        pSound->createNewChannel(&m_pEndSound);
        m_pEndSound->setLoopCount(-1);
        m_pEndSound->setMode(FMOD_LOOP_NORMAL);
        m_pEndSound->setPaused(false);

        CMenu *pWinnerMenu(CMenuContainer::getInstance()->getMenu(
            "winner_menu"));
        pWinnerMenu->setActive(true);

        vector<CEntity*> arrEntities(
            CEntityContainer::getInstance()->getEntityList(ENT_PLAYER));

        for (vector<CEntity*>::iterator pEntity(arrEntities.begin());
            pEntity != arrEntities.end(); ++pEntity)
        {
            CPlayer *pPlayer(dynamic_cast<CPlayer*>(*pEntity));
            pPlayer->showStats();
        }
    }
}

void CMap::showWinner(CEntity *pEntity)
{
    if (!m_bPaused)
    {
        m_nEndState = SINGLE_WINNER;

        endGame();

        m_pWinner = pEntity;

        pEntity->getDisplayable()->setAnimationSet(3);

        CCamera *pCamera(CCamera::getInstance());

        D3DXVECTOR3 currentPosition(pCamera->getPosition());
        D3DXVECTOR3 vecEntityPos(m_pWinner->getX(), m_pWinner->getY(),
            m_pWinner->getZ());
        D3DXVECTOR3 vecBetween(currentPosition - vecEntityPos);

        float distance(sqrt(vecBetween.x * vecBetween.x +
            vecBetween.z * vecBetween.z)); 
        vecBetween.x /= distance;
        m_fCameraRotationAngle = 6.28318531f - acos(vecBetween.x);

        // Interpolate the look at over 1 rotation
        vecEntityPos.y += 64.0f;
        m_vecLookatInterpolation = vecEntityPos - pCamera->getLookAt();
        m_vecLookatInterpolation /= (6.28318531f / rotation_speed);
        if (isDemo())
            m_vecLookatInterpolation *= 0.1f;

        // Interpolate to make it increase by 2PI over 1 rotation length
        m_fDistanceInterpolation = (distance - 300.0f) / 
            (6.28318531f / rotation_speed);

        if (isDemo())
            m_fDistanceInterpolation *= 0.1f;

        m_fHeightInterpolation = (currentPosition.y - m_pWinner->getY() - 200.0f)
            / (6.28318531f / rotation_speed);

        if (isDemo())
            m_fHeightInterpolation *= 0.1f;

        vecEntityPos.y += 640.0f;

        CLight *pLight(CLightContainer::getInstance()->getLight("spotlight"));
        pLight->setPosition(vecEntityPos);

        CMenu *pWinnerMenu(CMenuContainer::getInstance()->getMenu(
            "winner_menu"));
        m_pEndBackground->show();

        stringstream sstream;
        sstream << "Player "
                << (pEntity->getSubID() - ENT_PLAYER_1 + 1)
                << " wins!";
        pWinnerMenu->setTitle(sstream.str(), 0.0f, -150.0f);

        if (pEntity->getSubID() == ENT_PLAYER_1)
        {
            m_pEndBackground->setTexture(
                "textures/ui/game/pause_screen_pink.png");
            pWinnerMenu->setActiveColour(255, 0, 246, 180);
            pWinnerMenu->setColour(255, 0, 246, 180);

            pLight->setAmbient(1.0f, 0.1f, 1.0f, 1.0f);
            pLight->setDiffuse(1.0f, 0.2f,  1.0f, 1.0f);
        }
        else if (pEntity->getSubID() == ENT_PLAYER_2)
        {
            m_pEndBackground->setTexture(
                "textures/ui/game/pause_screen_red.png");
            pWinnerMenu->setActiveColour(255, 0, 0, 180);
            pWinnerMenu->setColour(255, 0, 0, 180);

            pLight->setAmbient(1.0f, 0.1f, 0.1f, 1.0f);
            pLight->setDiffuse(1.0, 0.2f,  0.2f, 1.0f);
        }
        else
        {
            pWinnerMenu->setActiveColour(127, 127, 127, 180);
            pWinnerMenu->setColour(127, 127, 127, 180);
            m_pEndBackground->setTexture(
                "textures/ui/game/pause_screen_neutral.png");
        }

        CLightContainer::getInstance()->setLight("spotlight", 1, 
            CDeviceManager::getInstance()->getDevice());
        CLightContainer::getInstance()->enableLight(1, 
            CDeviceManager::getInstance()->getDevice());
    }
}

void CMap::drawMatch(bool bAnyAlivePlayers)
{
    endGame();
    if (bAnyAlivePlayers)
    {
        m_nEndState = DRAW_WITH_WINNERS;
        vector<CEntity*> arrEntities(
            CEntityContainer::getInstance()->getEntityList(ENT_PLAYER));

        for (vector<CEntity*>::iterator pEntity(arrEntities.begin());
             pEntity != arrEntities.end(); ++pEntity)
        {
            CPlayer *pPlayer(dynamic_cast<CPlayer*>(*pEntity));
            if (pPlayer->isAlive())
            {
                pPlayer->updateScore(1);
            }
        }
    }
    else
    {
        m_nEndState = DRAW_NO_WINNERS;
    }
    m_vecLookatInterpolation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    CCamera *pCamera(CCamera::getInstance());

    D3DXVECTOR3 currentPosition(pCamera->getPosition());

    // On a draw, 
    D3DXVECTOR3 vecEntityPos(Constants::TILE_SIZE * getWidth() / 2.0f, 
        Constants::TILE_SIZE * getHeight(), 
        Constants::TILE_SIZE * getDepth() / 2.0f);
    D3DXVECTOR3 vecBetween(currentPosition - vecEntityPos);

    float distance(sqrt(vecBetween.x * vecBetween.x +
        vecBetween.z * vecBetween.z)); 
    vecBetween.x /= distance;
    m_fCameraRotationAngle = 6.28318531f - acos(vecBetween.x);

    m_fDistanceInterpolation = 0.0f;
    m_fHeightInterpolation = 0.0f;

    CMenu *pWinnerMenu(CMenuContainer::getInstance()->getMenu(
        "winner_menu"));
    pWinnerMenu->setTitle("DRAW", 0.0f, -150.0f);
    pWinnerMenu->setActiveColour(127, 127, 127, 180);
    pWinnerMenu->setColour(127, 127, 127, 180);
    m_pEndBackground->show();
    m_pEndBackground->setTexture(
        "textures/ui/game/pause_screen_neutral.png");
}

void CMap::setSkyBox(string szSkyboxPath)
{
    CSkyBox::getInstance()->changeSkybox(szSkyboxPath);
}

short int CMap::getTileID(int nHeight, int nWidth, int nDepth)
{
    return m_arrTileIDs.at(nHeight).at(nWidth).at(nDepth);
}

void CMap::setTile(int nHeight, int nWidth, int nDepth, CTile *pNewTile)
{
    CTile *pTile(nullptr);
    try
    {    
        pTile = m_MapData.at(nHeight).at(nWidth).at(nDepth);
    }
    catch(std::out_of_range)
    {
        // Trying to see a value out of range
        return;
    }
    if (pTile != nullptr)
    {
        if (pTile->getID() == ENT_TILE_FREE &&
            pNewTile->getID() != ENT_TILE_FREE)
        {
            // Tile to be deleted is a free tile, need to hide
            // the spaces either side of the object.

            if (nHeight > 0)
            {
                CTile *pHiddenTile(m_MapData.at(nHeight - 1).at(nWidth).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetNonDrawable(
                    TILE_FACE_TOP);
            }
            if (nHeight < static_cast<signed int>(m_MapData.size() - 1))
            {
                CTile *pHiddenTile(m_MapData.at(nHeight + 1).at(nWidth).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetNonDrawable(
                    TILE_FACE_BOTTOM);
            }

            if (nWidth > 0)
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth - 1).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetNonDrawable(
                    TILE_FACE_RIGHT);
            }
            if (nWidth < static_cast<signed int>(m_MapData.at(
                nHeight).size() - 1))
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth + 1).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetNonDrawable(
                    TILE_FACE_LEFT);
            }

            if (nDepth > 0)
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth).at(
                    nDepth - 1));
                pHiddenTile->getDisplayable()->setSubsetNonDrawable(
                    TILE_FACE_BACK);
            }
            if (nDepth < static_cast<signed int>(m_MapData.at(nHeight).at(
                nWidth).size() 
                - 1))
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth).at(
                    nDepth + 1));
                pHiddenTile->getDisplayable()->setSubsetNonDrawable(
                    TILE_FACE_FRONT);
            }
        }

        else if (pTile->getID() != ENT_TILE_FREE &&
                    pNewTile->getID() == ENT_TILE_FREE)
        {
            // Tile to be deleted is not a free tile, need to
            // add in the drawable faces of the objects around.
            if (nHeight > 0)
            {
                CTile *pHiddenTile(m_MapData.at(nHeight - 1).at(nWidth).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetDrawable(
                    TILE_FACE_TOP);
            }
            if (nHeight < static_cast<signed int>(m_MapData.size() - 1))
            {
                CTile *pHiddenTile(m_MapData.at(nHeight + 1).at(nWidth).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetDrawable(
                    TILE_FACE_BOTTOM);
            }

            if (nWidth > 0)
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth - 1).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetDrawable(
                    TILE_FACE_RIGHT);
            }
            if (nWidth < static_cast<signed int>(m_MapData.at(
                nHeight).size() - 1))
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth + 1).at(
                    nDepth));
                pHiddenTile->getDisplayable()->setSubsetDrawable(
                    TILE_FACE_LEFT);
            }

            if (nDepth > 0)
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth).at(
                    nDepth - 1));
                pHiddenTile->getDisplayable()->setSubsetDrawable(
                    TILE_FACE_BACK);
            }
            if (nDepth < static_cast<signed int>(m_MapData.at(nHeight).at(
                nWidth).size() 
                - 1))
            {
                CTile *pHiddenTile(m_MapData.at(nHeight).at(nWidth).at(
                    nDepth + 1));
                pHiddenTile->getDisplayable()->setSubsetDrawable(
                    TILE_FACE_FRONT);
            }
                    
        }
        delete pTile;
        pTile = nullptr;
    }    

    m_MapData.at(nHeight).at(nWidth).at(nDepth) = pNewTile;
    
}

void CMap::deleteEntityFromTile(CEntity *pEntity)
{
    for (MAP::const_iterator pFloor(m_MapData.begin());
        pFloor != m_MapData.end(); ++pFloor)
    {
        for (FLOOR::const_reverse_iterator pRow(pFloor->rbegin());
            pRow != pFloor->rend(); ++pRow)
        {
            for (ROW::const_reverse_iterator pTile(pRow->rbegin());
                pTile != pRow->rend(); ++pTile)
            {
                if ((*pTile) != nullptr )
                {
                    // Tick the tile
                    (*pTile)->removeEntityFromBlock(pEntity);
                }
            }
        }
    }
}

CEntity *CMap::getEntityFromID(int index)
{
    try
    {
        return m_arrEntitys.at(index);
    }
    catch (std::out_of_range)
    {
        return nullptr;
    }
}

bool CMap::isValidTile(unsigned int nHeight, unsigned int nWidth, 
    unsigned int nDepth) const
{
    if (m_MapData.size() > nHeight &&
        m_MapData.at(nHeight).size() > nWidth &&
        m_MapData.at(nHeight).at(nWidth).size() > nDepth)
    {
        return true;
    }
    return false;
}

void CMap::tick()
{
    m_DropTileRepeat.tick();
    m_CounterRepeat.tick();
    m_moveCameraRepeat.tick();
    //m_StartRepeatTimer.tick();

    if (m_nEndState != IN_PROGRESS || m_bDemo)
    {
        CCamera *pCamera(CCamera::getInstance());

        if (isDemo())
            m_fCameraRotationAngle += rotation_speed * 0.1f;
        else
            m_fCameraRotationAngle += rotation_speed;


        pCamera->setLookAt(pCamera->getLookAt() + m_vecLookatInterpolation);

        D3DXVECTOR3 vecBetween;

        D3DXVECTOR3 vecPosition(pCamera->getPosition());
        D3DXVECTOR3 vecEntityPos(Constants::TILE_SIZE * getWidth() / 2.0f, 
            Constants::TILE_SIZE * getHeight(), 
            Constants::TILE_SIZE * getDepth() / 2.0f);

        if (m_nEndState == SINGLE_WINNER)
        {
            m_pWinner->getPosition(vecEntityPos.x, vecEntityPos.y, vecEntityPos.z);
        }

        D3DXVec3Subtract(&vecBetween, &vecEntityPos, &vecPosition);
        vecBetween.y = 0.0f;

        float fLength(D3DXVec3Length(&vecBetween));

        if (! isDemo())
        {
            CLight *pGlobal(CLightContainer::getInstance()->getLight("sunlight"));
            float r(0.0f), g(0.0f), b(0.0f), a(0.0f);
            pGlobal->getAmbient(r, g, b, a);
            if (r > 0.1f)
            {
                r = max(r - 0.8f / (6.28318531f / rotation_speed / (isDemo() ? 10.0f : 1.0f)), 0.1f);
                g = max(g - 0.8f / (6.28318531f / rotation_speed / (isDemo() ? 10.0f : 1.0f)), 0.1f);
                b = max(b - 0.7f / (6.28318531f / rotation_speed / (isDemo() ? 10.0f : 1.0f)), 0.1f);

                pGlobal->setAmbient(r, g, b, a);

                pGlobal->getDiffuse(r, g, b, a);

                r = max(r - 0.8f / (6.28318531f / rotation_speed / (isDemo() ? 10.0f : 1.0f)), 0.1f);
                g = max(g - 0.8f / (6.28318531f / rotation_speed / (isDemo() ? 10.0f : 1.0f)), 0.1f);
                b = max(b - 0.7f / (6.28318531f / rotation_speed / (isDemo() ? 10.0f : 1.0f)), 0.1f);

                pGlobal->setDiffuse(r, g, b, a);

                CLightContainer::getInstance()->setLight("sunlight", 0, 
                    CDeviceManager::getInstance()->getDevice());
            }
        }

        float fMaxLength(300.0f);
        if (m_nEndState != SINGLE_WINNER)
        {
            fMaxLength = fLength;
        }

        if (fLength > fMaxLength)
        {
            fLength = max(fMaxLength, fLength - m_fDistanceInterpolation);
        }

        if (vecPosition.y > vecEntityPos.y + 200.0f)
        {
            vecPosition.y = max(vecPosition.y - m_fHeightInterpolation, 
                                     vecEntityPos.y + 200.0f);
        }
        else
        {
            vecEntityPos.y += 64.0f;
            pCamera->setLookAt(vecEntityPos);
            m_vecLookatInterpolation.x = 0.0f;
            m_vecLookatInterpolation.y = 0.0f;
            m_vecLookatInterpolation.z = 0.0f;
        }
        vecPosition.x = cos(m_fCameraRotationAngle) * fLength + vecEntityPos.x;
        vecPosition.z = sin(m_fCameraRotationAngle) * fLength + vecEntityPos.z;

        pCamera->setPosition(vecPosition);

        pCamera->applyViewMatrix();
        pCamera->applyProjectionMatrix();
        
    }

    else
    {
        CEntityContainer *pEntities(CEntityContainer::getInstance());

        vector<CEntity*> arrPlayers(pEntities->getEntityList(ENT_PLAYER));

        CPlayer *pWinner(nullptr);

        int nAlivePlayers(0);

        for (vector<CEntity*>::iterator pEntity(arrPlayers.begin());
            pEntity != arrPlayers.end(); ++pEntity)
        {
            CPlayer *pCasted(dynamic_cast<CPlayer*>(*pEntity));
            if (pCasted->isAlive())
            {
                ++nAlivePlayers;
                pWinner = pCasted;
            }
        }
        if (nAlivePlayers == 1)
        {
            // Add 1 to the winners score
            pWinner->updateScore(1);

            showWinner(pWinner);
        }
        else if (nAlivePlayers == 0)
        {
            drawMatch(false);
        }
    }

    // Tick all titles
    for (MAP::const_iterator pFloor(m_MapData.begin());
        pFloor != m_MapData.end(); ++pFloor)
    {
        for (FLOOR::const_reverse_iterator pRow(pFloor->rbegin());
            pRow != pFloor->rend(); ++pRow)
        {
            for (ROW::const_reverse_iterator pTile(pRow->rbegin());
                pTile != pRow->rend(); ++pTile)
            {
                if ((*pTile) != nullptr )
                {
                    // Tick the tile
                    (*pTile)->tick();
                }
            }
        }
    }
}

void CMap::render() const
{
    for (MAP::const_iterator pFloor(m_MapData.begin());
         pFloor != m_MapData.end(); ++pFloor)
    {
        for (FLOOR::const_reverse_iterator pRow(pFloor->rbegin());
             pRow != pFloor->rend(); ++pRow)
        {
            for (ROW::const_reverse_iterator pTile(pRow->rbegin());
                 pTile != pRow->rend(); ++pTile)
            {
                if ((*pTile) != nullptr && 
                    (*pTile)->getDisplayable() != nullptr)
                {
                    (*pTile)->getDisplayable()->render();
                }
            }
        }
    }
}

void CMap::unload()
{
    /**
     * Unloads a map. This should clear up all the memory created by the map.
     */
    for (MAP::iterator pFloor(m_MapData.begin()); pFloor != m_MapData.end();
         ++pFloor)
    {
        for (FLOOR::iterator pRow(pFloor->begin()); pRow != pFloor->end();
             ++pRow)
        {
            for (ROW::iterator pTile(pRow->begin()); pTile != pRow->end();
                 ++pTile)
            {
                delete *pTile;
            }
        }
    }

    m_MapData.clear();

    CTextureContainer::getInstance()->clearTextures();
    CMeshContainer::getInstance()->clearMeshes();
    CDisplayableContainer::getInstance()->clearDisplayables();
    CEntityContainer::getInstance()->clearAllEntities();

    CUI::getInstance()->removeChild(m_pBackground);
    CUI::getInstance()->removeChild(m_pPauseBackground);
    CUI::getInstance()->removeChild(m_pEndBackground);

    m_pWinner = nullptr;

    m_DropTileRepeat.stop();
    m_CounterRepeat.stop();
    m_moveCameraRepeat.stop();

    CLight *pGlobal(CLightContainer::getInstance()->getLight("sunlight"));
    if (pGlobal != nullptr)
    {
        pGlobal->setAmbient(0.9f, 0.9f, 0.8f, 1.0f);
        pGlobal->setDiffuse(0.9f, 0.9f, 0.8f, 1.0f);

        CLightContainer::getInstance()->setLight("sunlight", 0, 
            CDeviceManager::getInstance()->getDevice());

        CLightContainer::getInstance()->disableLight(1, 
            CDeviceManager::getInstance()->getDevice());
    }

    m_pGameSound->stop();
    m_pEndSound->stop();
}

bool CMap::setNextTileDroppableIndex()
{
    if (m_fDroppableXMax == m_fDroppableXMin ||
        m_fDroppableZMax == m_fDroppableZMin)
    {
        return false;
    }

    if (m_nTileMovement == MOVE_UP)
    {
        // Going up
        m_fDroppableZPosition += Constants::TILE_SIZE;
        if (m_fDroppableZPosition > m_fDroppableZMax)
        {
            // We've reached the top, go right
            m_fDroppableXPosition += (m_fDroppableZPosition - m_fDroppableZMax);
            m_fDroppableZPosition = m_fDroppableZMax;

            m_fDroppableXMin += Constants::TILE_SIZE;
            m_nTileMovement = MOVE_RIGHT;
        }
    }

    else if (m_nTileMovement == MOVE_DOWN)
    {
        // Going down
        m_fDroppableZPosition -= Constants::TILE_SIZE;
        if (m_fDroppableZPosition < m_fDroppableZMin)
        {
            // We've reached the bottom, go left
            m_fDroppableXPosition -= (m_fDroppableZMin - m_fDroppableZPosition);
            m_fDroppableZPosition = m_fDroppableZMin;

            m_fDroppableXMax -= Constants::TILE_SIZE;

            m_nTileMovement = MOVE_LEFT;
        }
    }

    else if (m_nTileMovement == MOVE_LEFT)
    {
        // Going left
        m_fDroppableXPosition -= Constants::TILE_SIZE;
        if (m_fDroppableXPosition < m_fDroppableXMin)
        {
            // We've reach the left, go up
            m_fDroppableZPosition += (m_fDroppableXMin - m_fDroppableXPosition);
            m_fDroppableXPosition = m_fDroppableXMin;

            m_fDroppableZMin += Constants::TILE_SIZE;

            m_nTileMovement = MOVE_UP;
        }
    }

    else if (m_nTileMovement == MOVE_RIGHT)
    {
        // Going right
        m_fDroppableXPosition += Constants::TILE_SIZE;
        if (m_fDroppableXPosition > m_fDroppableXMax)
        {
            // We've reach the right, go down
            m_fDroppableZPosition -= (m_fDroppableXPosition - m_fDroppableXMax);
            m_fDroppableXPosition = m_fDroppableXMax;

            m_fDroppableZMax -= Constants::TILE_SIZE;

            m_nTileMovement = MOVE_DOWN;
        }
    }

    return true;
}

bool CMap::isPaused() const
{
    return m_bPaused;
}

bool CMap::isDemo() const
{
    return m_bDemo;
}

namespace mapfuncs
{
    void dropTiles(DELAY_PARAMS &params)
    {
        CRepeat<void> *pRepeat(
            GET_POINTER_FROM_PARAM(params, 0, CRepeat<void>));
        CMap *pMap(GET_POINTER_FROM_PARAM(params, 1, CMap));

        float *pXPosition(GET_POINTER_FROM_PARAM(params, 2, float));
        float *pZPosition(GET_POINTER_FROM_PARAM(params, 3, float));
        float fYPosition((pMap->getHeight() - 1) * 64.0f);

        int nHeight(0);
        int nWidth(0);
        int nDepth(0);

        float fDelay(60000.0f / 
            ((pMap->getWidth() - 2) *
            (pMap->getDepth() - 2)));

        pRepeat->setRepeatDelay(fDelay);

        pMap->getTilePositionFromCoord(nHeight, nWidth, nDepth,
            *pXPosition, fYPosition, *pZPosition, 0);
        if (pMap->isValidTile(nHeight, nWidth, nDepth))
        {
            CTile *pTile(pMap->getTile(nHeight, nWidth, nDepth));
            if (pTile->isWalkable() || pTile->isDestructible())
            {
                // None-solid tile.
                if (pTile->isDestructible())
                {
                    // destroy tile.
                    pTile->destroy();
                }
                // Create the entity
                CEntityContainer *pEntities(CEntityContainer::getInstance());
                CEntity *pDroppable(new CDroppableTile(*pXPosition, 
                    fYPosition + 640.0f, *pZPosition, *pXPosition, fYPosition, 
                    *pZPosition,  CTimer() + 1000.0f));
                pEntities->addEntity(pDroppable);
            }
        }

        if (! pMap->setNextTileDroppableIndex())
        {
            pRepeat->stop();
        }
    }

    void decrementTime(DELAY_PARAMS &params)
    {
        CMap *pMap(GET_POINTER_FROM_PARAM(params, 0, CMap));
        pMap->decrementTime();
    }
    void moveCamera(DELAY_PARAMS &params)
    {
        //static float angle(0.0f);
        //angle += 0.5f / 180.0f * 3.142f;

        CCamera *pCamera(CCamera::getInstance());
            
        D3DXVECTOR3 vecLook(pCamera->getLookAt());
        if (vecLook.y > 0)
        {
            float *pInterpolation(GET_POINTER_FROM_PARAM(params, 0, float));
            vecLook.y -= *pInterpolation;
/*#ifdef _DEBUG
            vecLook.y -= 100.0f;
#else
            //vecLook.y = sin(angle) * 800 + 800;
            vecLook.y -= 4.0f;
#endif*/
            //vecLook.x = cos(angle) * 500.0f + pCamera->getPosition().x;
            //vecLook.z = sin(angle) * 500.0f + pCamera->getPosition().z;
            pCamera->setLookAt(vecLook);
            pCamera->applyProjectionMatrix();
            pCamera->applyViewMatrix();
        }
}
};